package org.beaconframework.ext.jdbc;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.sql.DataSource;

import org.beaconframework.vaadin.ui.Refreshable;
import org.beaconframework.vaadin.util.VaadinContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.util.IndexedContainer;

public class IndexedJdbcContainer extends IndexedContainer implements Refreshable {

	Logger log = LoggerFactory.getLogger(IndexedJdbcContainer.class);
	private static final long serialVersionUID = -4169917552447729046L;

	PlatformTransactionManager transactionManager;
	DataSource dataSource;
	String sql;
	Map<String, Object> parameterMap = new ConcurrentHashMap<String, Object>();
	SqlParameterSource sqlParameterSource;

	public class InnerResultSetExtractor implements ResultSetExtractor<Integer> {

		@Override
		public Integer extractData(ResultSet rs) throws SQLException,
				DataAccessException {
			ResultSetMetaData metaData = rs.getMetaData();
			if (getContainerPropertyIds().size() == 0) {
				setupContainerProperties(metaData);
			}

			int count = 0;
			while (rs.next()) {
				count++;
				Object itemIndex = addItem();
				Item item = getItem(itemIndex);
				for (int j = 1; j <= metaData.getColumnCount(); j++) {
					String label = metaData.getColumnLabel(j);
					Property prop = getContainerProperty(itemIndex, label);
					if (prop != null) {
						prop.setValue(rs.getString(j));
					}
				}

			}
			return count;
		}

	}

	public void setupContainerProperties(ResultSetMetaData rsmd)
			throws SQLException {
		int colCount = rsmd.getColumnCount();
		for (int i = 1; i <= colCount; i++) {
			log.debug("adding column/property '{}'", rsmd.getColumnLabel(i));
			String name = rsmd.getColumnLabel(i);
			addContainerProperty(name, String.class, "");
		}

	}

	public void setSql(String sql) {
		this.sql = sql;
	}

	public Set<String> getParameterNames() {
		return Collections.unmodifiableSet(parameterMap.keySet());
	}

	public void removeParameter(String name) {
		parameterMap.remove(name);
	}

	public void setParameter(String name, Object val) {
		parameterMap.put(name, val);
	}

	public void clearParameters() {
		parameterMap.clear();
	}

	public void refresh() {
		if (transactionManager==null) {
			throw new NullPointerException("transactionManager cannot be null");
		}
		TransactionTemplate tt = new TransactionTemplate(transactionManager);

		tt.setReadOnly(true);
		TransactionCallbackWithoutResult tc = new TransactionCallbackWithoutResult() {

			@Override
			protected void doInTransactionWithoutResult(TransactionStatus status) {

				NamedParameterJdbcTemplate jt = new NamedParameterJdbcTemplate(
						dataSource);
				if (getSqlParameterSource() != null) {
					jt.query(getSql(), getSqlParameterSource(),
							new InnerResultSetExtractor());
				} else {
					jt.query(getSql(), parameterMap,
									new InnerResultSetExtractor());
				}
			}
		};
		tt.execute(tc);
		VaadinContext vc;
	}

	public PlatformTransactionManager getTransactionManager() {
		return transactionManager;
	}

	public void setTransactionManager(
			PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public String getSql() {
		return sql;
	}

	public SqlParameterSource getSqlParameterSource() {
		return sqlParameterSource;
	}

	public void setSqlParameterSource(SqlParameterSource sqlParameterSource) {
		this.sqlParameterSource = sqlParameterSource;
	}

}
